package com.bcs.moon;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import java.util.Map.Entry;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.FunctionNode;
import org.mozilla.javascript.Node;
import org.mozilla.javascript.Parser;
import org.mozilla.javascript.ScriptOrFnNode;
import org.mozilla.javascript.Token;
import org.mozilla.javascript.Node.Jump;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.bcs.compiler.Target;
import com.bcs.compiler.TargetSourceC;
import com.bcs.compiler.ValueBooleanConstant;
import com.bcs.compiler.ValueGlobal;
import com.bcs.compiler.ValueGlobalSlot;
import com.bcs.compiler.ValueLocal;
import com.bcs.compiler.ValueNullConstant;
import com.bcs.compiler.ValueNumberConstant;
import com.bcs.compiler.ValueScopeLocal;
import com.bcs.compiler.ValueStringConstant;
import com.bcs.compiler.ValueThis;
import com.bcs.compiler.ValueUndefinedConstant;

public abstract class XCompile {
	protected StackFrame currentFrame;
	protected Scope currentScope;
	int nextLabel;
	public boolean enableDebugging = true;
	Hashtable<Node, String> labelMap = new Hashtable<Node, String>();
	protected ArrayList<String> constantLines = new ArrayList<String>();
	protected ArrayList<String> constantDecls = new ArrayList<String>();
	Hashtable<Double, String> constantDoubleLookup = new Hashtable<Double, String>();
	Hashtable<String, String> constantStringLookup = new Hashtable<String, String>();

	Hashtable<String, Object> globalsMap = new Hashtable<String, Object>();
	
	protected ArrayList<String> internalFunctionNames = new ArrayList<String>();	
	
	protected ArrayList<String> breakpointScripts = new ArrayList<String>();
	protected ArrayList<Integer> breakpointLines = new ArrayList<Integer>();
	protected DocumentBuilder builder;
	

	protected PrintWriter binder;
	protected int maxArgs = 1;
	int baseArgs = 0;
	
	protected int nextFunctionId = 1;
	protected int gNextEventHandlerId;
	protected int nextGlobalId = 0;
	protected int currentLine = 0;
	protected String currentScript = "";
	static int INT_MIN = -1073741823;
	static int INT_MAX = 1073741823;
	
	protected Target target;
	
	public XCompile() {
		DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
		
		builderFactory.setValidating(false);
		builderFactory.setExpandEntityReferences(false);
		try {
		    builder = builderFactory.newDocumentBuilder();
		    builder.setEntityResolver(new NullEntityResolver());

		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		currentFrame = new StackFrame();
		currentScope = new Scope(currentFrame, null);
				
		List<String> intrinsics = Arrays.asList(
				"Function", //0
				"debugstr",
				"println",
				"parseInt",
				"Math",
				"String", // 5
				"Array",
				"Date",
				"Object",
				"UI",
				"HTTP", // 10
				"XML",
				"Curl",
				"System",
				"File",
				"event", // 15
				"Graphics",
				"Image",
				"Dir",
				"Java");
		
		for (int idx = 0; idx < intrinsics.size(); idx++)
			globalsMap.put(intrinsics.get(idx), new ValueGlobalSlot(idx));
		nextGlobalId = intrinsics.size();
		/*
		globalsMap.put("Function", new ValueGlobalSlot(0));
		globalsMap.put("debugstr", new ValueGlobal("debugstr"));
		globalsMap.put("println", new ValueGlobal("println"));
		globalsMap.put("parseInt", new ValueGlobal("parseInt"));
		globalsMap.put("Math", new ValueGlobal("Math"));
		globalsMap.put("String", new ValueGlobal("String"));
		globalsMap.put("Array", new ValueGlobal("Array"));
		globalsMap.put("Date", new ValueGlobal("Date"));
		globalsMap.put("Object", new ValueGlobal("Object"));
		globalsMap.put("UI", new ValueGlobal("UI"));
		globalsMap.put("HTTP", new ValueGlobal("HTTP"));
		globalsMap.put("XML", new ValueGlobal("XML"));
		globalsMap.put("Curl", new ValueGlobal("Curl"));
		globalsMap.put("System", new ValueGlobal("System"));
		globalsMap.put("File", new ValueGlobal("File"));
		globalsMap.put("event", new ValueGlobal("event"));
		globalsMap.put("Graphics", new ValueGlobal("Graphics"));
		globalsMap.put("Image", new ValueGlobal("Image"));
		globalsMap.put("Dir", new ValueGlobal("Dir"));
		globalsMap.put("Java", new ValueGlobal("Java"));
		*/
	}
	
	protected void defineGlobal(String name, int globId) {
		if (globalsMap.containsKey(name))
			throw new MoonException(currentLine, "Duplicate declaration of " + name);
		globalsMap.put(name, new ValueGlobalSlot(globId));
	}
	
	public void reset(Scope priorScope) {
		currentFrame = new StackFrame();
		currentScope = new Scope(currentFrame, priorScope);
		maxArgs = 1;
	}

	private static void dumpChildren(int indent, Node o) {
		for (Node n = o.getFirstChild(); n != null; n = n.getNext()) {
			for (int idx = 0; idx < indent; idx++)
				System.out.print(' ');
			if (n.getType() == Token.NUMBER)
				System.out.println(n.getDouble());
			else if (n.getType() == Token.NAME)
				System.out.println(n.getString());
			else if (n.getType() == Token.STRING)
				System.out.println("\"" + n.getString() + "\"");
			else if (n.getType() == Token.DIV)
				System.out.println("/");
			else if (n.getType() == Token.ADD)
				System.out.println("+");
			else
				System.out.println("?" + n.getType());
			dumpChildren(indent + 1, n);
		}
	}

	protected String localValue(int slot) {
		return "slot[" + slot + "]";
	}
	/*
	protected String getGlobalSlot(String name) {
		if (globalsMap.containsKey(name))
			return "cx->" + globalsMap.get(name);
		throw new MoonException(currentLine, "Unknown variable " + name);
	}
	*/
	protected Object getGlobalValue(String name) {
		if (globalsMap.containsKey(name))
			return globalsMap.get(name);
		throw new MoonException(currentLine, "Unknown variable " + name);
	}
	
	protected String targetLabel(Node n) {
		if (!labelMap.containsKey(n)) {
			String l = "label" + nextLabel++;
			labelMap.put(n, l);
		}
		return labelMap.get(n);
	}
	
	protected String encodeString(String s) {
		StringBuffer buf = new StringBuffer();
		buf.append("\"");
		for (int idx = 0; idx < s.length(); idx++) {
			char ch = s.charAt(idx);
			if (ch == '"')
				buf.append("\\\"");
			else if (ch == '\\')
				buf.append("\\\\");
			else if (ch == '\n')
				buf.append("\\n");
			else if (ch == '\r')
				buf.append("\\r");
			else if (ch == '\t')
				buf.append("\\t");
			else
				buf.append(ch);
		}
		buf.append("\"");
		return buf.toString();
	}
	
	protected String constantDouble(double v) {
		if (v == Math.floor(v) && v >=INT_MIN && v <= INT_MAX) {
			return "INT_TO_JSVAL(" + (int) v + ")";
		}

		String existing = constantDoubleLookup.get(v);
		if (existing != null) {
			return existing;
		}
		
		String constVar = "cx->slots[" + (nextGlobalId++) + "]";
	
		constantLines.add(constVar + " = jsnumconst(cx, " + v + ");");
		constantDoubleLookup.put(v, constVar);
		return constVar;
	}
	
	protected String constantString(String v) {
		String existing = constantStringLookup.get(v);
		if (existing != null) {
			return existing;
		}
		String constVar = "cx->slots[" + (nextGlobalId++) + "]";
		
		constantLines.add(constVar + " = jsstrconst(cx, L" + encodeString(v) + ", " + v.length() + ");");
//		constantDecls.add("jsvalue " + constVar + ";");
		constantStringLookup.put(v, constVar);
		return constVar;
	}
	
	public static int hash(String key) {
		int h = 0;
		for (int idx = 0; idx < key.length(); idx++) {
			h = (h << 1) ^ key.charAt(idx);
		}
		return h;
	}
	/*
	protected String nameToDirect(String name) {
		Integer localSlot = currentScope.getName(name);
		if (localSlot != null) {
			return localValue(localSlot);
		} else {
			String framePrefix = "frame->";
			for (Scope p = currentScope.previous; p != null; p = p.previous) {
				framePrefix += "outer->";
				localSlot = p.getName(name);
				if (localSlot != null) {
					return framePrefix + "slots[" + localSlot + "]";
				}
			}

			return getGlobalSlot(name);
		}
	}
*/
	protected Object nameToValue(String name) {
		Integer localSlot = currentScope.getName(name);
		if (localSlot != null) {
			return new ValueLocal(localSlot);
		} else {
			int depth = 0;
			for (Scope p = currentScope.previous; p != null; p = p.previous) {
				depth++;
				localSlot = p.getName(name);
				if (localSlot != null) {
					return new ValueScopeLocal(depth, localSlot);
				}
			}

			return getGlobalValue(name);
		}
	}

	
	protected boolean canDirectCompile(Node n) {
		if (n.getType() == Token.NUMBER
				|| n.getType() == Token.NAME
				|| n.getType() == Token.FALSE
				|| n.getType() == Token.STRING
				|| n.getType() == Token.TRUE
				|| n.getType() == Token.THIS
				|| n.getType() == Token.NULL) {
			return true;
		}
		return false;
	}
	
	private boolean canDirectCompileConstant(Node n) {
		if (n.getType() == Token.NUMBER
				|| n.getType() == Token.FALSE
				|| n.getType() == Token.STRING
				|| n.getType() == Token.TRUE
				|| n.getType() == Token.THIS
				|| n.getType() == Token.NULL) {
			return true;
		}
		return false;
	}
	/*
	protected String directCompile(Node n) {
		if (n.getType() == Token.NUMBER) {
			return constantDouble(n.getDouble());
		}
		if (n.getType() == Token.STRING) {
			return constantString(n.getString());
		}
		if (n.getType() == Token.NAME) {
			String str = n.getString();
			if (str.equals("undefined"))
				return "JSVAL_VOID";
			return nameToDirect(n.getString());
		} else if (n.getType() == Token.NULL) {
			return "JSVAL_NULL";
		} else if (n.getType() == Token.FALSE) {
			return "JSVAL_FALSE";
		} else if (n.getType() == Token.TRUE) {
			return "JSVAL_TRUE";
		} else if (n.getType() == Token.THIS) {
			return "me";
		}

		throw new UnimplementedException();
	}
	*/
	protected Object directValue(Node n) {
		if (n.getType() == Token.NUMBER) {
			return new ValueNumberConstant(n.getDouble());
		}
		if (n.getType() == Token.STRING) {
			return new ValueStringConstant(n.getString());
		}
		if (n.getType() == Token.NAME) {
			String str = n.getString();
			if (str.equals("undefined"))
				return ValueUndefinedConstant.INSTANCE;
			return nameToValue(n.getString());
		} else if (n.getType() == Token.NULL) {
			return ValueNullConstant.INSTANCE;
		} else if (n.getType() == Token.FALSE) {
			return ValueBooleanConstant.FALSE;
		} else if (n.getType() == Token.TRUE) {
			return ValueBooleanConstant.TRUE;
		} else if (n.getType() == Token.THIS) {
			return ValueThis.INSTANCE;
		}

		throw new UnimplementedException();
	}

	private void compileBinOp(Node n, int slot, String fn) {
		Node child = n.getFirstChild();
		compileExpression(child, slot);
		int tempSlot = currentFrame.getTempSlot();
		while ((child = child.getNext()) != null) {
			if (canDirectCompile(child)) {
				target.callBinOp(new ValueLocal(slot), fn, new ValueLocal(slot), directValue(child));
			} else {
				compileExpression(child, tempSlot);
				target.callBinOp(new ValueLocal(slot), fn, new ValueLocal(slot), new ValueLocal(tempSlot));
			}
		}
		currentFrame.releaseTempSlot(tempSlot);
	}
	
	protected void compileExpression(Node n, int slot) {
		if (canDirectCompile(n)) {
			target.assignLocal(slot, directValue(n));
		} else if (n.getType() == Token.ARRAYLIT) {
			int tempSlot = currentFrame.getTempSlot();
			target.callSimpleFunction(slot, "jsnewarray");

			for (Node p = n.getFirstChild(); p != null; p = p.getNext()) {
				if (canDirectCompile(p)) {
					target.callVoidRuntime("jspush", new ValueLocal(slot), directValue(p));
				} else {
					compileExpression(p, tempSlot);
					target.callVoidRuntime("jspush", new ValueLocal(slot), new ValueLocal(tempSlot));
				}
			}
			currentFrame.releaseTempSlot(tempSlot);
		} else if (n.getType() == Token.OBJECTLIT) {
			Object[] names = (Object[]) n.getProp(Node.OBJECT_IDS_PROP);
			target.callSimpleFunction(slot, "jsnewobject");
			int idx = 0;
			for (Node p = n.getFirstChild(); p != null; p = p.getNext()) {
				String name = (String) names[idx];
				if (canDirectCompile(p)) {
					target.invokeSet(new ValueLocal(slot), name, directValue(p));
				} else {
					int temp = currentFrame.getTempSlot();
					compileExpression(p, temp);
					target.invokeSet(new ValueLocal(slot), name, new ValueLocal(temp));
					currentFrame.releaseTempSlot(temp);
				}
				idx++;
			}
		} else if (n.getType() == Token.NEW) {
			int tempSlot = currentFrame.getTempSlot();
			Object classParam;
							
			compileExpression(n.getFirstChild(), tempSlot);
			classParam = new ValueLocal(tempSlot);
			
			Node child = n.getFirstChild();
			int argCount = 0;
			int argBase = baseArgs;
			while (null != (child = child.getNext())) {
				argCount++;
			}

			baseArgs += argCount;
			
			ArrayList<Object> params = new ArrayList<Object>();
			ArrayList<Integer> paramTemps = new ArrayList<Integer>();
			
			child = n.getFirstChild();
			int paramIdx = 0;
			while (null != (child = child.getNext())) {
				if (canDirectCompile(child) && child.getType() != Token.NAME) {
					params.add(directValue(child));
//					out.println("callFrame[" + (argBase + paramIdx) + "] = " + directCompile(child) + ";");
				} else {
					int temp = currentFrame.getTempSlot();
					paramTemps.add(temp);
					compileExpression(child, temp);
					params.add(new ValueLocal(temp));
//					out.println("callFrame[" + (argBase + paramIdx) + "] = " + localValue(argSlot) + ";");
				}
				paramIdx++;
			}
			if (baseArgs > maxArgs)
				maxArgs = baseArgs;
			
			baseArgs -= argCount;

			target.invokeNew(new ValueLocal(slot), classParam, params);
			for (Integer t : paramTemps) {
				currentFrame.releaseTempSlot(t);
			}
			
//			out.println(localValue(slot) + " = jsnew(cx, " + classParam + ", callFrame + " + argBase + ", " + paramIdx + ");");
			currentFrame.releaseTempSlot(tempSlot);
		} else if (n.getType() == Token.NOT) {
			int tempSlot = currentFrame.getTempSlot();
			compileExpression(n.getFirstChild(), tempSlot);
			target.callUnaryOp(new ValueLocal(slot), "jsnot", new ValueLocal(tempSlot));

			currentFrame.releaseTempSlot(tempSlot);
		} else if (n.getType() == Token.NEG) {
			Node child = n.getFirstChild();
			if (canDirectCompile(child)) {
				target.callUnaryOp(new ValueLocal(slot), "jsneg", directValue(child));
			} else {
				int tempSlot = currentFrame.getTempSlot();
				compileExpression(n.getFirstChild(), tempSlot);
				target.callUnaryOp(new ValueLocal(slot), "jsneg", new ValueLocal(tempSlot));
				currentFrame.releaseTempSlot(tempSlot);
			}
		} else if (n.getType() == Token.CALL) {
			Node child = n.getFirstChild();
			int tempSlot = currentFrame.getTempSlot();
			int objSlot = -1;
			Object thisValue = ValueUndefinedConstant.INSTANCE;
			
			if (child.getType() == Token.GETPROP) {
				Node obj = child.getFirstChild();
				Node field = obj.getNext();
				objSlot = currentFrame.getTempSlot();
				compileExpression(obj, objSlot);
				target.invokeGet(new ValueLocal(tempSlot), new ValueLocal(objSlot), field.getString());
				thisValue = new ValueLocal(objSlot);
			} else {
				compileExpression(child, tempSlot);
			}

			int argCount = 0;
			int argBase = baseArgs;
			while (null != (child = child.getNext())) {
				argCount++;
			}
			
			ArrayList<Object> params = new ArrayList<Object>();
			ArrayList<Integer> paramTemps = new ArrayList<Integer>();
			
			baseArgs += argCount;
			child = n.getFirstChild();
			int argIdx = 0;
			while (null != (child = child.getNext())) {
				if (canDirectCompileConstant(child)) {
					params.add(directValue(child));
				} else {
					int temp = currentFrame.getTempSlot();
					paramTemps.add(temp);
					params.add(new ValueLocal(temp));
					compileExpression(child, temp);
				}
				argIdx++;
			}
			
			if (baseArgs > maxArgs)
				maxArgs = baseArgs;
			baseArgs -= argCount;

			target.invokeInvoke(new ValueLocal(slot), new ValueLocal(tempSlot), thisValue, params);
//			out.println(localValue(slot) + " = jsinvoke(cx, " + localValue(tempSlot) + ", " + thisValue + ", callFrame + " + argBase + ", " + argIdx + ");");
			
			currentFrame.releaseTempSlot(tempSlot);
			if (objSlot != -1)
				currentFrame.releaseTempSlot(objSlot);
		} else if (n.getType() == Token.ADD) {
			compileBinOp(n, slot, "jsadd");
		} else if (n.getType() == Token.LSH) {
			compileBinOp(n, slot, "jslsh");
		} else if (n.getType() == Token.RSH) {
			compileBinOp(n, slot, "jsrsh");
		} else if (n.getType() == Token.URSH) {
			compileBinOp(n, slot, "jsursh");
		} else if (n.getType() == Token.EQ) {
			compileBinOp(n, slot, "jseq");
		} else if (n.getType() == Token.NE) {
			compileBinOp(n, slot, "jsne");
		} else if (n.getType() == Token.SHEQ) {
			compileBinOp(n, slot, "jseqeq");
		} else if (n.getType() == Token.SHNE) {
			compileBinOp(n, slot, "jsnene");
		} else if (n.getType() == Token.LE) {
			compileBinOp(n, slot, "jsle");
		} else if (n.getType() == Token.LT) {
			compileBinOp(n, slot, "jslt");
		} else if (n.getType() == Token.GE) {
			compileBinOp(n, slot, "jsge");
		} else if (n.getType() == Token.GT) {
			compileBinOp(n, slot, "jsgt");
		} else if (n.getType() == Token.SUB) {
			compileBinOp(n, slot, "jssub");
		} else if (n.getType() == Token.DIV) {
			compileBinOp(n, slot, "jsdiv");
		} else if (n.getType() == Token.MUL) {
			compileBinOp(n, slot, "jsmul");
		} else if (n.getType() == Token.AND) {
			compileBinOp(n, slot, "jsand");
		} else if (n.getType() == Token.OR) {
			compileBinOp(n, slot, "jsor");
		} else if (n.getType() == Token.GETELEM) {
			Node child = n.getFirstChild();
			Node idxChild = child.getNext();
			
			if (canDirectCompile(child) && canDirectCompile(idxChild)) {
				target.invokeGetElem(new ValueLocal(slot), directValue(child), directValue(idxChild));
//				out.println(localValue(slot) + " = jsgetelem(cx, " + directCompile(child) + "," + directCompile(idxChild) + ");");
			} else if (canDirectCompile(idxChild)) {
				int tempSlot = currentFrame.getTempSlot();
				
				compileExpression(child, tempSlot);
				
				target.invokeGetElem(new ValueLocal(slot), new ValueLocal(tempSlot), directValue(idxChild));
				
				currentFrame.releaseTempSlot(tempSlot);
			} else {
				int tempSlot = currentFrame.getTempSlot();
				int tempSlot2 = currentFrame.getTempSlot();
				
				compileExpression(child, tempSlot);
				compileExpression(idxChild, tempSlot2);
				
				target.invokeGetElem(new ValueLocal(slot), new ValueLocal(tempSlot), new ValueLocal(tempSlot2));
				
				currentFrame.releaseTempSlot(tempSlot2);
				currentFrame.releaseTempSlot(tempSlot);
			}
		} else if (n.getType() == Token.SETELEM) {
			int tempSlot = currentFrame.getTempSlot();
			int tempSlot2 = currentFrame.getTempSlot();

			Node child = n.getFirstChild();
			Node child2 = child.getNext();
			Node child3 = child2.getNext();
			compileExpression(child, tempSlot);
			compileExpression(child2, tempSlot2);
			compileExpression(child3, slot);

			target.invokeSetElem(new ValueLocal(tempSlot), new ValueLocal(tempSlot2), new ValueLocal(slot));

			currentFrame.releaseTempSlot(tempSlot2);
			currentFrame.releaseTempSlot(tempSlot);
		} else if (n.getType() == Token.SETNAME) {
			Node child = n.getFirstChild();
			compileExpression(child.getNext(), slot);
			String ident = child.getString();
			if (ident.equals("undefined"))
				throw new RuntimeException("Cannot assigned to undefined");

			target.assign(nameToValue(ident), new ValueLocal(slot));
		} else if (n.getType() == Token.GETPROP) {
			Node child = n.getFirstChild();

			if (canDirectCompile(child)) {
				Object base = directValue(child);

				child = child.getNext();
				target.invokeGet(new ValueLocal(slot), base, child.getString());
			} else {
				int temp = currentFrame.getTempSlot();

				compileExpression(child, temp);

				child = child.getNext();
				target.invokeGet(new ValueLocal(slot), new ValueLocal(temp), child.getString());
				currentFrame.releaseTempSlot(temp);
			}
		} else if (n.getType() == Token.SETPROP) {
			Node child = n.getFirstChild();
			if (canDirectCompile(child)) {
				Object base = directValue(child);
				child = child.getNext();
				
				compileExpression(child.getNext(), slot);
	
				target.invokeSet(base, child.getString(), new ValueLocal(slot));
			} else {
				int temp = currentFrame.getTempSlot();
				
				compileExpression(child, temp);
				child = child.getNext();
				
				compileExpression(child.getNext(), slot);
	
				target.invokeSet(new ValueLocal(temp), child.getString(), new ValueLocal(slot));
				currentFrame.releaseTempSlot(temp);
			}
		} else if (n.getType() == Token.GET_REF) {
			Node child = n.getFirstChild();
			
			if (child.getType() == Token.REF_MEMBER) {
				int propType = child.getIntProp(Node.MEMBER_TYPE_PROP, 0);
				
				if (0 != (propType & Node.ATTRIBUTE_FLAG)) {
					child = child.getFirstChild();
					
					int temp = currentFrame.getTempSlot();
					
					compileExpression(child, temp);
					child = child.getNext();

					target.invokeGetAttr(new ValueLocal(slot), new ValueLocal(temp), child.getString());
					currentFrame.releaseTempSlot(temp);
				} else if (0 != (propType & Node.DESCENDANTS_FLAG)) {
					child = child.getFirstChild();
					
					int temp = currentFrame.getTempSlot();
					
					compileExpression(child, temp);
					child = child.getNext();
							
					target.invokeGetDesc(new ValueLocal(slot), new ValueLocal(temp), child.getString());
					currentFrame.releaseTempSlot(temp);
				} else {
					throw new UnimplementedException();
				}
			} else {
				throw new UnimplementedException();
			}
			
		} else if (n.getType() == Token.INC) {
			Node child = n.getFirstChild();
			boolean preinc = 0 == (n.getIntProp(Node.INCRDECR_PROP, 0) & Node.POST_FLAG);
			if (child.getType() == Token.NAME) {
				if (!preinc)
					target.assign(new ValueLocal(slot), nameToValue(child.getString()));
				target.callUnaryOp(nameToValue(child.getString()), "jsinc", nameToValue(child.getString()));
				if (preinc)
					target.assign(new ValueLocal(slot), nameToValue(child.getString()));
			} else if (child.getType() == Token.GETPROP) {
				Node c = child.getFirstChild();
				int temp = currentFrame.getTempSlot();
				
				compileExpression(c, temp);

				c = c.getNext();
				target.invokeGet(new ValueLocal(slot), new ValueLocal(temp), c.getString());
				target.callUnaryOp(new ValueLocal(slot), "jsinc", new ValueLocal(slot));
				target.invokeSet(new ValueLocal(temp), c.getString(), new ValueLocal(slot));
				currentFrame.releaseTempSlot(temp);
			} else {
				throw new UnimplementedException();
			}
		} else if (n.getType() == Token.DEC) {
			Node child = n.getFirstChild();
			boolean predec = 0 == (n.getIntProp(Node.INCRDECR_PROP, 0) & Node.POST_FLAG);
			if (child.getType() == Token.NAME) {
				if (!predec)
					target.assign(new ValueLocal(slot), nameToValue(child.getString()));
				target.callUnaryOp(nameToValue(child.getString()), "jsdec", nameToValue(child.getString()));
				if (predec)
					target.assign(new ValueLocal(slot), nameToValue(child.getString()));
			} else if (child.getType() == Token.GETPROP) {
				Node c = child.getFirstChild();
				int temp = currentFrame.getTempSlot();
				
				compileExpression(c, temp);

				c = c.getNext();
				target.invokeGet(new ValueLocal(slot), new ValueLocal(temp), c.getString());
				target.callUnaryOp(new ValueLocal(slot), "jsdec", new ValueLocal(slot));
				target.invokeSet(new ValueLocal(temp), c.getString(), new ValueLocal(slot));
				currentFrame.releaseTempSlot(temp);
			} else {
				throw new UnimplementedException();
			}
		} else if (n.getType() == Token.FUNCTION) {
			String name = internalFunctionNames.get(n.getExistingIntProp(1));
			target.assignClosure(new ValueLocal(slot), name);
//			out.println(localValue(slot) +  "= jsclosure(cx, code_" + name + ", frame);");
		} else {
			System.out.println("unknown exp node " + n.getType());
			dumpChildren(2, n);
			throw new UnimplementedException();
		}
	}

	private void establishGlobalVar(Node n) {
		for (Node o = n.getFirstChild(); o != null; o = o.getNext()) {
			int ot = o.getType();
			if (ot == Token.NAME) {
				int globId = nextGlobalId++;
				defineGlobal(o.getString(), globId);
			} else {
				throw new UnimplementedException();
			}
		}
	}
	
	
	protected boolean inUserScript() {
		if (currentScript == null || currentScript.length() == 0)
			return false;
		if (currentScript.equals("_internal"))
			return false;
		return true;
	}
	
	public void setRelease() {
		enableDebugging = false;
	}
	

	protected CompilerEnvirons compilerEnv;
	protected ErrorReporter errorReporter;
	protected PrintWriter writer;
	protected StringWriter bindString;
	protected StringWriter fileContent;

	public void initialize() {
		compilerEnv = new CompilerEnvirons();
		errorReporter = new ErrorReporter() {

			public void error(String message, String sourceName, int line,
					String lineSource, int lineOffset) {
				throw new MoonException(line, message);
			}

			public EvaluatorException runtimeError(String message,
					String sourceName, int line, String lineSource,
					int lineOffset) {
				System.out.println(message);
				return null;
			}

			public void warning(String message, String sourceName, int line,
					String lineSource, int lineOffset) {
				// TODO Auto-generated method stub
				System.out.println(message);
			};
		};
		
		target.initializeTarget();
		
	}

	private void establishGlobalsForCode(Node node) {
		// establish any global variables
		for (Node decl = node.getFirstChild(); decl != null; decl = decl.getNext()) {
			currentLine = decl.getLineno();
			if (decl.getType() == Token.VAR) {
				establishGlobalVar(decl);
			} else if (decl.getType() == Token.LOOP || decl.getType() == Token.BLOCK) {
				establishGlobalsForCode(decl);
			}
		}
	}
	
	private void establishGlobals(ScriptOrFnNode node) {
		establishGlobalsForCode(node);
		// establish any global functions
		int subCount = node.getFunctionCount();
		for (int idx = 0; idx < subCount; idx++) {
			FunctionNode fn = node.getFunctionNode(idx);
			String fnname = fn.getFunctionName();
			if (!fnname.equals("")) {
				int globId = nextGlobalId++;
				defineGlobal(fnname, globId);
			}
		}
	}
	
	protected void compileVar(Node n, boolean scriptLevel) {
		for (Node o = n.getFirstChild(); o != null; o = o.getNext()) {
			int ot = o.getType();
			if (ot == Token.NAME) {
				if (scriptLevel) {
					if (o.getFirstChild() != null) {
						if (canDirectCompile(o.getFirstChild())) {
							target.assign(getGlobalValue(o.getString()),  directValue(o.getFirstChild()));
						} else {
							int slot = currentFrame.getTempSlot();
							compileExpression(o.getFirstChild(), slot);
							target.assign(getGlobalValue(o.getString()),  new ValueLocal(slot));
							currentFrame.releaseTempSlot(slot);
						}
					}
				} else {
					int varIdx = currentFrame.nextSlot++;
					currentScope.addLocal(o.getString(), varIdx);
					if (o.getFirstChild() != null)
						compileExpression(o.getFirstChild(), varIdx);
				}
			} else {
				throw new UnimplementedException();
			}
		}
	}
	
	protected ArrayList<ScriptOrFnNode> scriptNodes = new ArrayList<ScriptOrFnNode>();
	protected ArrayList<String> scriptNames = new ArrayList<String>();
	public boolean hasError;
	public String errorScript;
	public int errorLine;
	public String errorMessage;
	
	public void parseScript(String scriptName, String script) {
		try {
			Parser parser = new Parser(compilerEnv, errorReporter);
	
			ScriptOrFnNode node = parser.parse(
					script, "src://none", 1);
			scriptNodes.add(node);
			scriptNames.add(scriptName);
			
			establishGlobals(node);
		} catch (MoonException ex) {
			hasError = true;
			errorLine = ex.line;
			errorScript = scriptName;
			errorMessage = ex.description;
		} catch (Exception ex) {
			hasError = true;
			errorLine = -1;
			errorScript = scriptName;
			errorMessage = "An unexpected error occurred: " + ex.toString();	
		}
	}

	protected Object getPropertyDictionaryEntry(Element rootDict, String keyName) {
		NodeList children = rootDict.getChildNodes();
		int count = children.getLength();

		int mode = 0;
		String key = null;
		
		for (int idx = 0; idx < count; idx++) {
			org.w3c.dom.Node n = children.item(idx);
			if (n instanceof Element) {
				Element el = (Element) n;
				if (mode == 0) {
					String nodeName = el.getNodeName();
					if (!nodeName.equals("key"))
						throw new RuntimeException("Malformed property list");
					key = el.getTextContent();
					mode = 1;
				} else {
					String valueType = el.getNodeName();
					if (key.equals(keyName)) {
						if (valueType.equals("string"))
							return el.getTextContent();
						else if (valueType.equals("integer"))
							return Integer.parseInt(el.getTextContent());
						else
							return null;
					}
					mode = 0;
				}
			}
		}
		return null;
	}

	protected String getControlName(Element rootDict) {
		return (String) getPropertyDictionaryEntry(rootDict, "name");
	}
	
	public void addFile(String path) {
		String name = path;
		int sepidx = path.lastIndexOf(File.separatorChar);
		if (sepidx >= 0) {
			name = path.substring(sepidx + 1);
		}
		if (path.endsWith(".jsfrm")) {
			parseForm(path, name, name.substring(0, name.length() - 6));
		} else if (path.endsWith(".js")) {
			try {
				parseScript(name, readTextFile(path));
			} catch (IOException ex) {
				hasError = true;
				errorLine = -1;
				errorScript = path;
				errorMessage = "An unexpected error occurred: " + ex.toString();	
			}
		} else if (path.endsWith(".png")) {
			String baseName = name.substring(0, name.length() - 4);
			int globId = nextGlobalId++;
			defineGlobal(baseName, globId);
			addImageRef(globId, name, path);
		}
	}
	
	protected ArrayList<FunctionNode> functionNodes = new ArrayList<FunctionNode>();
	protected ArrayList<String> functionNames = new ArrayList<String>();
	protected ArrayList<String> functionScripts = new ArrayList<String>();
		
	protected boolean isPriorityFile(String file) {
		if (file.equals("icmForm.js"))
			return true;
		return false;
	}
	

	public void compile(String targetFile) {
		handleCompile(targetFile, "runMain");
	}

	private static String readTextFile(String path) throws IOException {
		FileReader reader = new FileReader(path);
		char[] buf = new char[8000];
		
		int len = reader.read(buf, 0, 8000);
		if (len <= 0)
			return "";
		return new String(buf, 0 , len);
	}
	
	public static void main(String[] args)  throws IOException {
		int baseIdx = 0;
		String mainFn = "runMain";
		XCompile compiler = new TargetSourceC();
		compiler.initialize();
		compiler.setRelease();

		if (baseIdx < args.length && args[baseIdx].startsWith("-fn=")) {
			mainFn = args[baseIdx].substring(4);
			baseIdx++;
		}
		for (int idx = baseIdx + 1; idx < args.length; idx++) {
			String path = args[idx];
			if (path.endsWith(".jsfrm")) {
				String name = path;
				int sepidx = path.lastIndexOf(File.separatorChar);
				if (sepidx >= 0) {
					name = path.substring(sepidx + 1);
				}
				compiler.parseForm(path, name, name.substring(0, name.length() - 6));
			} else
				compiler.parseScript(path, readTextFile(args[idx]));
			if (compiler.hasError) {
				System.out.println(args[idx] + ": " + compiler.errorMessage + " on line " + compiler.errorLine);
				System.exit(1);
			}
		}
		compiler.handleCompile(args[baseIdx], mainFn);
		if (compiler.hasError) {
			System.out.println(compiler.errorScript + ": " + compiler.errorMessage + " on line " + compiler.errorLine);
			System.exit(1);
		}
	}

	protected String getDebugFrame() {
		StringBuilder result = new StringBuilder();
		boolean first = true;
		for (Entry<String, Integer> entry : currentScope.lookup.entrySet()) {
			if (first)
				first = false;
			else
				result.append(',');
			result.append(entry.getKey());
			result.append('=');
			result.append(entry.getValue());
		}
		return result.toString();
	}
	
	abstract public void handleCompile(String targetFile, String mainFunction);
	abstract public void parseForm(String scriptPath, String scriptName, String formName);
	abstract public Object parseFunction(String scriptName, String script, String fnname);
	abstract public void addImageRef(int globid, String name, String path);

}
